package flows

import (
	"archive/zip"
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
	"zimuzu_cvn_web_api/config"
	"zimuzu_cvn_web_api/model"
	"zimuzu_cvn_web_api/pkg/drive/redis"
	"zimuzu_cvn_web_api/pkg/e"
	"zimuzu_cvn_web_api/pkg/errno"
	"zimuzu_cvn_web_api/service"
)

/* Redis key 相关封装 */
func UserTokenRedisKey(uid int64) string {
	return e.UserRedisPrefix + strconv.FormatInt(uid, 10) + e.RedisUserToken
}

// 流量上报
// source: 2-网心 1-钛雾
// type: 1-Linux 2-路由器
// Auth：hzp
func Report(Uid, t int64, Id string, Type, Source int, Mac, IpStr string) (err error) {

	e.Log("info", "流量上报参数为：uid=", Uid, "&Id=", Id, "&Type=", Type, "&Mac=", Mac, "&Source=", Source)

	// 校验节点是否存在
	nodeId, err := model.CheckFlowsNode(Uid, Type, Mac, IpStr)
	if err != nil {
		return err
	}

	// 网心的不上报
	if Source == 2 {
		return nil
	}

	// 流量上报
	err = model.CreateReportRecord(Id, nodeId, t, Source)
	if err != nil {
		return err
	}

	return nil
}

// 用户token相关
// Auth: hzp
func AddUserToken(uid int64, client int, registerRationId string) (err error, token string) {

	if uid < 1 {
		errno.ParamsEmpty.Info += "{ uid }"
		e.Log("Info", errno.ParamsEmpty.Info)
		return errno.ParamsEmpty, ""
	}

	// 先判断redis是否已经存在了10条记录
	tokenKey := UserTokenRedisKey(uid)
	rCount, err := redis.ZCount(tokenKey, 0, time.Now().Unix())
	if err != nil {
		return err, ""
	}
	if rCount >= 10 {
		// 删掉最前面的redis记录
		e.Log("info", "token redis 超过10条记录,[", rCount, "]")
		needDeleteNum := rCount - 9
		deleteToken, _ := redis.ZRange(tokenKey, 0, needDeleteNum-1)
		for i := 0; i < needDeleteNum; i++ {
			reply, err := redis.ZRem(tokenKey, deleteToken[i])
			if err != nil {
				e.Log("error", "token 删除redis数据失败,", err.Error())
			}
			if reply <= 0 {
				e.Log("error", "token 删除redis数据失败,No error")
			}
		}
	}

	// 再判断mysql是否已经存在10条记录
	count, err := model.GetUserTokenCount(uid)
	if err != nil {
		e.Log("Info", "Token 已经超过10个,且删除token失败", err.Error())
		return err, ""
	}
	if count >= 10 {
		err = model.DeleteUserToken(uid, count-9)
		if err != nil {
			e.Log("error", "删除Token失败", err.Error())
			return err, ""
		}
	}

	// 随机生成Token
	token = e.Md5Str(strconv.FormatInt(e.MicroTime(), 10) + strconv.Itoa(e.RandInt(1000, 9999)))
	var userToken model.UserToken
	userToken.Uid = uid
	userToken.Client = client
	userToken.Token = token
	userToken.RegisterRationId = registerRationId

	// 写入mysql
	err = model.AddUserToken(&userToken)
	if err != nil {
		e.Log("Info", "写入token失败", err.Error())
		return err, ""
	}

	// 写入redis
	redis.ZAdd(tokenKey, token, time.Now().Unix())

	return nil, token
}

func CheckNode(uid int64, mac string) (nodeId int64, nodeName string, err error) {

	re, nodeName, err := GetNodeByMac(uid, mac)
	if err != nil {
		return 0, nodeName, err
	}
	if re > 0 {
		return re, nodeName, nil
	}

	listNum, err := model.GetNodeByUidMacRecords(uid)
	if err != nil {
		return 0, nodeName, err
	}
	name := "yyets_" + strconv.Itoa(listNum+1)
	nodeId, name, err = model.CreateNode(uid, name, mac)
	if err != nil {
		return 0, name, err
	}

	return nodeId, name, nil
}

// 一个Mac只能绑定一个UID用户
func GetNodeByMac(uid int64, mac string) (nodeId int64, name string, err error) {

	var res model.NodeMacModel
	cacheKey := e.PublicRedisPrefix + "node:mac:" + mac
	redis.UnDelete(cacheKey)
	if redis.Exists(cacheKey) {
		nodeIdStr, err := redis.Get(cacheKey)
		if err != nil {
			return 0, "", err
		}
		if nodeIdStr == "0" {
			_, _ = redis.Delete(cacheKey)
		} else {
			err = json.Unmarshal([]byte(nodeIdStr), &res)
			if err != nil {
				return 0, "", err
			}

			return res.NodeId, res.Name, nil
		}
	}

	res, err = model.GetNodeIdByMac(mac)
	if err != nil {
		return 0, "", err
	}
	if res.NodeId <= 0 {
		return 0, "", nil
	}
	if res.Uid > 0 && uid != res.Uid {
		e.Log("err", "check [uid=", uid, "] and [mac=", mac, " ]err!!!")
		return res.NodeId, "", errno.IllegalErr
	}

	saveStr, _ := json.Marshal(res)
	_, _ = redis.Set(cacheKey, saveStr, e.TimeFormatMin*5)
	return res.NodeId, res.Name, nil
}

// 校验用户token
func CheckUserToken(uid int64, token string) (error, bool) {

	if uid < 1 {
		return errno.ParamsEmpty, false
	}

	// 校验缓存是否存在
	tokenKey := UserTokenRedisKey(uid)
	bo, redisErr := redis.ZIsMember(tokenKey, token)

	// 校验DB是否存在
	find, _ := model.GetUserTokenByUidAndToken(uid, token)

	// 缓存和DB同时存在
	if redisErr == nil && bo && find > 0 {
		return nil, true
	}

	// 缓存不存在 和DB存在
	if find > 0 {
		// 找到了写入到redis
		redis.ZAdd(tokenKey, token, time.Now().Unix())
		return nil, true
	}

	// DB 不存在，说明缓存失效
	return errno.RecordError, false
}

// 节点状态上报
// Auth: hzp
func ReportStatus(data model.NodeStatusReportParams) (err error) {

	// 校验uid+nodeId是否合法
	var statusInfo model.CheckNodeInfo
	checkKey := e.NodeBindUserCache + data.NodeId + ":" + strconv.FormatInt(data.Uid, 10)
	if !redis.Exists(checkKey) {
		statusInfo, err = model.CheckNodeByUidAndNodeId(data.Uid, data.NodeId)
		if err != nil {
			return err
		}
		redis.SetUnRe(checkKey, 1, e.TimeFormatMin*4)
		statusByte, _ := json.Marshal(statusInfo)
		redis.SetUnRe(checkKey+":statusInfo", string(statusByte), e.TimeFormatMin*4)
	} else {
		if !redis.Exists(checkKey + ":statusInfo") {
			redis.UnDelete(checkKey)
			e.Log("info", "redis key error.")
			return err
		}
		statusStr, err := redis.Get(checkKey + ":statusInfo")
		if err != nil {
			return err
		}
		err = json.Unmarshal([]byte(statusStr), &statusInfo)
		if err != nil {
			return err
		}
	}

	// 状态上报
	var freeSize int64
	var currentStatus int
	currentStatus = 0
	redisKey := e.NodeFlowSourceCache + data.NodeId
	var setMap map[string]interface{}
	setMap = make(map[string]interface{}, len(data.List))
	for _, k := range data.List {
		SourceStr := strconv.Itoa(k.Source)
		setMap[SourceStr] = k.Status
		if k.Status >= 1 {
			currentStatus = 1
		}
		freeSize += k.FreeSize
	}

	// 更新在线时长
	var updateOnlineTime int
	nowTime := time.Now().Hour()*60 + time.Now().Minute()
	if statusInfo.OnlineTime+5 > nowTime {
		updateOnlineTime = nowTime
	} else {
		updateOnlineTime = statusInfo.OnlineTime + 5
	}
	historyOnlineTime := service.UpdateOnlineTimeDB(statusInfo.HistoryOnlineTime, updateOnlineTime)
	// 更新对应的 状态信息
	err = model.UpdateMgNodeStatus(data.NodeId, currentStatus, updateOnlineTime, data.NatType, historyOnlineTime, freeSize)
	if err != nil {
		e.Log("info", "更新节点状态失败："+err.Error())
		return err
	}

	bo, err := redis.HSet(redisKey, setMap, -1)
	if bo && err == nil {
		return nil
	}
	e.Log("info", "节点状态更新完成")

	return errno.RedisSetErr
}

// 钛雾流量读取
// Auth: hzp
func WriteTaiWuFlows(flows model.TailWuFlowParams, reports model.ReportDB) (err error) {
	var sqlListStr []string
	var totalFlow float64
	var dataTime int64
	for _, k := range flows.Upload.Flow {
		totalFlow += k.Flow / 1024
		dataTime = k.Time / 1000
	}
	timeInt64 := e.StrToTime(time.Unix(dataTime, 0).Format("2006-01-02 15:00:00"))
	sqlStr := model.GetUpdateNodeFlowSql(reports.NodeId, totalFlow, reports.Type, reports.Source, timeInt64)
	if sqlStr != "" {
		sqlListStr = append(sqlListStr, sqlStr)
	}
	// sql数组准备好了
	err = model.BatchUpdateNodeFlowSql(sqlListStr)
	if err != nil {
		e.Log("err", "批量队列更新失败:", err)
		return
	}
	e.Log("info", len(sqlListStr), "条数据已经处理完成")
	return nil
}

// 单节点质押&解除操作
// Auth: hzp
func SingleNodePledge(Uid, NodeId int64, Way int) (err error) {

	e.Log("info", "开始处理：node_id=", NodeId, " & way= ", Way, " & uid = ", Uid)

	// 判断节点ID 和Uid是否匹配
	nodeInfo, err := model.GetNodeInfoByUidAndNodeId(Uid, NodeId)
	if err != nil {
		return err
	}

	if nodeInfo.TokenStatus == 20 {
		return errno.UnPledgeTokenErr
	}

	// 判断当前状态是否满足
	// Way: 处理方式，2-质押 3-解除质押
	var Source int
	if Way == 2 {
		Source = model.LogSourcePledge
		if nodeInfo.Status == 2 {
			return errno.DeleteNodePledgeErr
		} else if nodeInfo.TokenStatus != 1 {
			return errors.New("仅非质押状态节点可执行质押操作")
		}
	} else if Way == 3 {
		Source = model.LogSourcePledgeRedemption
		if nodeInfo.TokenStatus == 1 {
			return errors.New("未质押状态节点不可操作解除质押")
		}
	}

	// 开启事务
	var rowId2, rowId3 int64
	conn, err := config.C.DB.DB.Beginx()
	if err != nil {
		e.Log("err", "质押节点开启事务失败", err.Error())
		return errors.New("质押节点失败，请稍后重试")
	}

	Cvn := e.SinglePledgeCvn
	if Way == 2 {
		e.Log("info", "进入质押操作")
		// 质押操作
		// 修改amount,插入cvn流水,
		// 扣除余额
		updateAmountData := service.UpdateCvnAmountParams{
			Uid:          Uid,
			Amount:       -Cvn,
			Source:       Source,
			FrozenAmount: Cvn,
			RelateId:     strconv.FormatInt(nodeInfo.NodeId, 10),
			RelateTable:  "mg_node",
		}
		_, err = service.UpdateCvnAmountBalance(conn, updateAmountData)
		if err != nil {
			conn.Rollback()
			return err
		}
		// 增加质押记录
		err = service.UpdateCvnFrozenAmount(conn, Uid, 0, Cvn, model.PledgeLinux)
		if err != nil {
			conn.Rollback()
			return err
		}
		rowId3 = 1
	} else {
		e.Log("info", "进入解除质押操作")
		// 设置解除质押的计划任务
		// 增加计划任务
		err = service.PledgeDraw(conn, Uid, Cvn, model.PledgeLinux, NodeId)
		if err != nil {
			conn.Rollback()
			return err
		}
		rowId3 = 1
	}

	// 更新mg_node
	r2, err2 := conn.Exec("UPDATE mg_node SET token_status = ?, pledge_time = UNIX_TIMESTAMP(now()) "+
		" WHERE node_id = ? AND uid = ? AND status != 2", Way, NodeId, Uid)
	if err2 == nil {
		rowId2, err2 = r2.RowsAffected()
		if err2 != nil {
			e.Log("err", "SinglePledge 更新mg_node表失败(RowsAffected)，nodeId = ", NodeId, err2.Error())
			rowId2 = 0
		}
	} else {
		e.Log("err", "SinglePledge 更新mg_node表失败，nodeId = ", NodeId, err2.Error())
	}

	// 提交事务
	if rowId2 > 0 && rowId3 > 0 {
		err = conn.Commit()
		if err != nil {
			e.Log("err", "SinglePledge 事务提交失败", err.Error())
			err = conn.Rollback()
			if err != nil {
				e.Log("err", "SinglePledge 事务回滚失败", err.Error())
			}
			return errors.New("质押失败，请稍后重试")
		}
		e.Log("info", "质押令牌成功：uid-", Uid, " nodeId-", NodeId, " tokenStatus-", Way)
	} else {
		e.Log("info", "质押失败 rowId2:", rowId2, ":rowId3:", rowId3)
		err = conn.Rollback()
		if err != nil {
			e.Log("err", "SinglePledge 事务回滚失败", err.Error())
		}
		return errors.New("质押失败***********")
	}

	e.Log("info", "处理完成：node_id=", NodeId, " & way= ", Way, " & uid = ", Way)
	return nil
}

func GetUserNodeListKey(Uid int64, Per, Page int) (cacheKey string) {
	return e.TempUserNodeList + ":" + strconv.FormatInt(Uid, 10) + ":" + strconv.Itoa(Per) + "_" + strconv.Itoa(Page)
}
func DeleteUserNodeListKey(Uid int64) {
	var cacheKey string
	for i := 1; i < 100; i++ {
		cacheKey = e.TempUserNodeList + ":" + strconv.FormatInt(Uid, 10) + ":" + "20_" + strconv.Itoa(i)
		if redis.Exists(cacheKey) {
			redis.UnDelete(cacheKey)
		} else {
			break
		}
	}
}

// 节点列表数据接口
// Auth: hzp
func GetUserNodeList(Uid int64, Per, Page int) (res []model.UserNodeInfo, err error) {

	// 读取缓存，每隔一个小时更新一次缓存
	cacheKey := GetUserNodeListKey(Uid, Per, Page)
	if redis.Exists(cacheKey) {
		rD, _ := redis.Get(cacheKey)
		err = json.Unmarshal([]byte(rD), &res)
		if err != nil {
			redis.UnDelete(cacheKey)
			return res, err
		}
		return res, nil
	}

	node, err := model.GetUserNodeList(Uid, Per, Page)
	if err != nil {
		return res, err
	}

	// 获取用户相关流量
	flows, err := model.GetUserNodeListFlowsByNodeList(Uid)
	if err != nil {
		return res, err
	}
	var flowsList map[int64]int64
	flowsList = make(map[int64]int64)
	for _, kk := range flows {
		if kk.NodeId <= 0 {
			continue
		}
		flowsList[kk.NodeId] = kk.Up
	}

	// 临时结果集
	var temp model.UserNodeInfo
	for _, k := range node {
		temp.NodeId = k.NodeId
		temp.Type = k.Type
		temp.Name = k.Name
		temp.Offline = k.Offline
		temp.OnlineTime = k.OnlineTime
		temp.Process = k.Process
		temp.Storage = k.Storage
		temp.TokenStatus = k.TokenStatus
		temp.PublicIp = k.PublicIp
		if _, ok := flowsList[k.NodeId]; ok {
			temp.Up = flowsList[k.NodeId]
		} else {
			temp.Up = 0.00
		}
		// 状态说明：status 状态，0-未开启 1-未绑定 2-运行中 3-已停止
		if k.Status == 1 {
			if k.Offline > 0 || k.OnlineTime <= 0 {
				temp.Status = 3
			} else if len(k.PublicIp) == 0 {
				temp.Status = 1
				temp.OnlineTime = 0
			} else {
				temp.Status = 2
			}
		} else {
			temp.Status = k.Status
			temp.OnlineTime = 0
		}
		res = append(res, temp)
	}

	rD, _ := json.Marshal(res)
	redis.SetUnRe(cacheKey, string(rD), e.TimeFormatMin*5)

	return res, nil
}

// 加入超级节点
// Auth: hzp
func UserJoinSuperNode(SuperUid, Uid int64) (err error) {

	// 校验超级节点是否正确
	super, err := model.SuperNodeInfoByUid(SuperUid)
	if err != nil {
		return errno.ErrInviteCode
	}
	if super.NodeId <= 0 {
		return errno.ErrInviteCode
	}

	// 校验是否已经成为旷工
	superNodeId, err := model.GetSuperNodeMinerByUid(Uid)
	if err != nil {
		return err
	}
	if superNodeId > 0 {
		return errno.MinerExistsErr
	}
	err = model.NormalUserApplyMiner(Uid, super.NodeId)
	if err != nil {
		return err
	}
	return nil
}

// 查询旷工身份
// Auth: hzp
func GetMinerRole(Uid int64) (res model.MinerRoleResponse, err error) {

	// 初始化数据
	res.Role = 1
	res.SuperNodeId = 0
	role, err := model.GetSuperMinerRole(Uid)
	if err != nil {
		return res, nil
	}

	if role.Uid == role.SuperUid {
		res.Role = 3
	} else if role.Status == 20 {
		res.Role = 2
		res.SuperNodeId = role.SuperUid
	}

	return res, nil
}

// https://titan-public.oss-cn-shanghai.aliyuncs.com/cvnt-log/cvnt_flow.log.2020-03-27_12.zip
// 获取钛雾接口流量数据
// 每隔一个小时取上一个小时的流量数据
// Auth: hzp
func GetTwLastDayFlow(curHour int, yesterdayStr string) (err error) {
	e.Log("info", "请求钛雾接口流量为：hour=", curHour)
	var hourStr string
	if curHour == 0 {
		hourStr = "23"
		if yesterdayStr == "" {
			yesterdayStr = time.Now().AddDate(0, 0, -1).Format(e.TimeFormatD)
		}
	} else {
		if curHour < 11 {
			hourStr = "0" + strconv.Itoa(curHour-1)
		} else {
			hourStr = strconv.Itoa(curHour - 1)
		}
		if yesterdayStr == "" {
			yesterdayStr = time.Now().Format(e.TimeFormatD)
		}
	}
	downFileName := yesterdayStr + "_" + hourStr + ".zip"
	httpStr := "https://titan-public.oss-cn-shanghai.aliyuncs.com/cvnt-log/cvnt_flow.log."
	urlStr := httpStr + downFileName

	resp, err := http.Get(urlStr)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	downDir := "./log/data/" + yesterdayStr + "/"
	resDir, err := e.MakeDir(downDir)
	if err != nil || !resDir {
		e.Log("err", "创建日志目录 ["+downDir+"] 失败")
		return errors.New("创建日志目录 [" + downDir + "] 失败")
	}
	e.DelLogDataPath("log/data/")

	downFilePath := downDir + downFileName
	// 判断当前文件是否已经取过，否则跳过
	_, err = os.Stat(downFilePath)
	if err == nil {
		return nil
	}
	downFile, err := os.Create(downFilePath)
	if err != nil {
		return err
	}
	defer downFile.Close()
	// 复制网上zip文件到本地
	io.Copy(downFile, bytes.NewReader(b))

	srcFile, err := os.Open(downFilePath)
	if err != nil {
		return err
	}
	defer srcFile.Close()
	newFile, err := DeCompress(srcFile, downDir)
	if err != nil {
		return err
	}

	// 复制文件和写入到指定文件
	err = DealTwData(newFile)
	if err != nil {
		return err
	}
	return nil
}

// 每天凌晨处理昨天在线时长不够的结点，将结点设置为离线状态
// Auth: hzp
func DealOfflineNode() error {

	// 结点类型
	// 1、最后更新时间小于一小时，在线时长大于12个小时，不处理
	// 2、最后更新时间小于一小时，在线时长小于12个小时，做离线一天处理
	// 3、最后更新时间大于一小时，在线时长大于12个小时，不处理
	// 4、最后更新时间大于一小时，在线时长小于12个小时，做离线一天处理
	// 5、最后更新时间小于今天12点之前，做离线一天处理

	affectedNum1, err := model.DealOfflineNodeWithAfter12Hour()
	if err != nil {
		return err
	}
	e.Log("info", "处理长时间不在线记录：处理成功"+strconv.FormatInt(affectedNum1, 10)+"条记录")

	affectedNum2, err := model.DealOfflineNodeWithBlow12Hour()
	if err != nil {
		return err
	}
	e.Log("info", "处理离线时长超过12个小时的情况：处理成功"+strconv.FormatInt(affectedNum2, 10)+"条记录")

	return nil
}

/**
@zipFile：压缩文件
@dest：解压之后文件保存路径
*/
func DeCompress(srcFile *os.File, dest string) (newFileName string, err error) {
	zipFile, err := zip.OpenReader(srcFile.Name())
	if err != nil {
		e.Log("info", err.Error())
		return newFileName, err
	}
	defer zipFile.Close()
	for _, innerFile := range zipFile.File {
		info := innerFile.FileInfo()
		if info.IsDir() {
			err = os.MkdirAll(innerFile.Name, os.ModePerm)
			if err != nil {
				e.Log("info", err.Error())
				return newFileName, err
			}
			continue
		}
		srcFile, err := innerFile.Open()
		if err != nil {
			e.Log("info", err.Error())
			continue
		}
		defer srcFile.Close()
		e.Log("info", innerFile.Name)
		newFileName = dest + innerFile.Name
		newFile, err := os.Create(newFileName)
		if err != nil {
			e.Log("info", err.Error())
			continue
		}
		io.Copy(newFile, srcFile)
		newFile.Close()
	}
	return newFileName, nil
}

func DealTwData(fileName string) (err error) {

	file, err := os.Open(fileName)
	if err != nil {
		err = errors.New("流量日志读取异常")
		return err
	}
	// 先获取到所有数据
	flowsNode, err := model.GetAllNodeIdForReport()
	if err != nil {
		return err
	}
	if len(flowsNode) <= 0 {
		err = errors.New("没有获取到上报数据")
		return err
	}
	var nodeMap map[string]model.ReportDB
	nodeMap = make(map[string]model.ReportDB)
	for _, node := range flowsNode {
		nodeMap[node.ReportId] = node
	}

	// 逐行读取数据
	var i, k, h, total int
	var flowJson model.TailWuFlowParams
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		total++
		tempList := strings.Split(scanner.Text(), " ")
		if len(tempList) != 3 {
			i++
			e.Log("err", "数据格式异常：")
			continue
		}
		// 看看string解析是否能满足flows格式
		err := json.Unmarshal([]byte(tempList[2]), &flowJson)
		if err != nil {
			i++
			e.Log("err", "数据格式异常：", err.Error())
			continue
		}
		if _, ok := nodeMap[flowJson.PId]; !ok {
			h++
			e.Log("err", "忽略更新：", flowJson.PId, " 不属于人人节点, 共有", h)
			continue
		}
		if nodeMap[flowJson.PId].NodeId <= 0 {
			h++
			e.Log("err", "忽略更新：", flowJson.PId, " 不属于人人节点, 共有", h)
			continue
		}
		nodeIdStr := strconv.FormatInt(nodeMap[flowJson.PId].NodeId, 10)
		e.Log("info", "准备写入节点[node_id="+nodeIdStr+", &pid="+flowJson.PId+"]数据")
		err = WriteTaiWuFlows(flowJson, nodeMap[flowJson.PId])
		if err != nil {
			i++
			e.Log("err", "更新失败：", err.Error())
		} else {
			k++
			e.Log("info", "数据正常解析共：", k, "次, 异常", i, "次")
		}
	}
	e.Log("info", "处理完成：共有节点:", total, ",忽略:", h, ", 处理完成:", k, ",失败:", i)
	return nil
}

// 热力图接口
func GetHeatDistribution() (res []model.HeatDistributionStruct, err error) {

	// 热力图每天取一次数据
	redisKey := e.PublicRedisPrefix + "heat:distribution"
	if redis.Exists(redisKey) {
		// 先取出redis数据, 再解析到相应的格式上
		redisDataStr, err := redis.Get(redisKey)
		if err != nil {
			return res, err
		}
		err = json.Unmarshal([]byte(redisDataStr), &res)
		if err != nil {
			return res, err
		}
		return res, nil
	}

	response, err := model.GetHeatDistributionDb()
	if err != nil {
		return res, err
	}
	provinceTempList := [34]string{"河北省", "山西省", "辽宁省", "吉林省", "黑龙江省", "江苏省", "浙江省", "安徽省", "福建省",
		"江西省", "山东省", "河南省", "湖北省", "湖南省", "广东省", "海南省", "四川省", "贵州省", "云南省", "陕西省", "甘肃省",
		"青海省", "台湾省", "北京", "天津", "上海", "重庆", "广西", "内蒙古", "西藏", "宁夏", "新疆", "香港", "澳门"}

	provinceList := make(map[string]int, 34)
	for i := 0; i < 34; i++ {
		provinceList[provinceTempList[i]] = 1
	}

	// 初始化一个临时存储temp
	var temp model.HeatDistributionStruct
	for _, k := range response {
		if _, ok := provinceList[k.Name]; ok {
			temp.Name = k.Name
			temp.Selected = true
			temp.Value = k.Value
			delete(provinceList, k.Name)
			res = append(res, temp)
		}
	}

	// 将未出现的地图位置标示出来
	for vv, _ := range provinceList {
		temp.Name = vv
		temp.Selected = false
		temp.Value = 0
		res = append(res, temp)
	}
	// 设置缓存数据
	b, err := json.Marshal(res)
	if err != nil {
		return res, err
	}
	redis.SetUnRe(redisKey, string(b), e.TimeFormatMin)
	return res, nil
}

// 获取用户结点详情
func GetUserNodeProfitDetail(LxId int64) (res []model.ProfitDetailResponse, err error) {

	ListData, err := model.GetProfitRecordById(LxId)
	if err != nil {
		return res, err
	}
	if ListData.Id <= 0 {
		return res, errno.RecordError
	}

	res, err = model.GetUserProfitDetailByListIdFromDB(ListData.Uid, ListData.Date)
	if err != nil {
		return res, err
	}
	return res, nil
}

// 下载文件
func DownTheFileFromX(urlStr string) (err error) {

	downFileName := e.Md5Str(e.GetRandomNum(6)) + path.Ext(urlStr)
	resp, err := http.Get(urlStr)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	e.Log("info", "读取文件成功:"+downFileName)
	downDir := "./log/pic/"
	resDir, err := e.MakeDir(downDir)
	if err != nil || !resDir {
		return errors.New("创建日志目录 [" + downDir + "] 失败")
	}

	downFilePath := downDir + downFileName
	// 判断当前文件是否已经取过，否则跳过
	_, err = os.Stat(downFilePath)
	if err == nil {
		e.Log("info", "文件存在，忽略读取:", downFilePath)
		return nil
	}
	downFile, err := os.Create(downFilePath)
	if err != nil {
		return err
	}

	defer downFile.Close()
	reader := bufio.NewReaderSize(resp.Body, 32*1024)
	// 复制网上zip文件到本地
	writer := bufio.NewWriter(downFile)
	written, _ := io.Copy(writer, reader)
	e.Log("info", "file length:", written)

	return nil
}

// 软删除节点
func DeleteOfflineNodeByNodeId(Uid, NodeId int64) (err error) {

	nodeInfo, err := model.GetNodeInfoByUidAndNodeId(Uid, NodeId)
	if err != nil {
		return err
	}
	if nodeInfo.TokenStatus > 1 {
		return errno.DeleteNodeStatusErr
	}
	if nodeInfo.Status == 2 {
		DeleteUserNodeListKey(Uid)
		return nil
	}

	if nodeInfo.Status == 1 && nodeInfo.Process > 0 {
		return errno.DeleteNodeStatusErr
	}
	err = model.UpdateMgNodeOffline(NodeId)
	if err != nil {
		return err
	}
	// 删除完成只会清除用户第一页缓存数据，让用户从新获取数据
	DeleteUserNodeListKey(Uid)
	return nil
}
